#pragma once

#include "ops.h"

namespace sfc::variant {

template <class T>
concept trivial = __is_trivial(T);

template <class... T>
struct Variant;

template <trivial T0, trivial T1>
struct Variant<T0, T1> {
  u8 _tag;
  union Inn {
    T0 _0;
    T1 _1;
  } _inn;

  [[sfc_inline]] Variant(const T0& val) noexcept : _tag{0}, _inn{._0 = val} {}
  [[sfc_inline]] Variant(const T1& val) noexcept : _tag{1}, _inn{._1 = val} {}
};

template <class T0, class T1>
struct Variant<T0, T1> {
  u8 _tag;
  union Inn {
    T0 _0;
    T1 _1;
    Nil _2;
    [[sfc_inline]] ~Inn() noexcept {}
  } _inn;

  [[sfc_inline]] Variant(T0&& val) : _tag{0}, _inn{._0 = static_cast<T0&&>(val)} {}
  [[sfc_inline]] Variant(T1&& val) : _tag{1}, _inn{._1 = static_cast<T1&&>(val)} {}

  [[sfc_inline]] ~Variant() {
    if (_tag == 0) _inn._0.~T0();
    if (_tag == 1) _inn._1.~T1();
  }

  [[sfc_inline]] Variant(const Variant& other) : _tag{other._tag} {
    if (_tag == 0) new (__builtin_addressof(_inn._0)) T0{other._inn._t0};
    if (_tag == 1) new (__builtin_addressof(_inn._1)) T1{other._inn._t1};
  }

  [[sfc_inline]] Variant(Variant&& other) noexcept : _tag{other._tag}, _inn{._2 = {}} {
    if (_tag == 0) new (__builtin_addressof(_inn._0)) T0{static_cast<T0&&>(other._inn._0)};
    if (_tag == 1) new (__builtin_addressof(_inn._1)) T1{static_cast<T1&&>(other._inn._1)};
  }
};

}  // namespace sfc::variant

#ifndef __clang__
template <unsigned I, class... T>
struct _type_pack_element_t;

template <class T, class... U>
struct _type_pack_element_t<0, T, U...> {
  using Type = T;
};

template <unsigned I, class T, class... U>
struct _type_pack_element_t<I, T, U...> : _type_pack_element_t<I - 1, U...> {};

template <unsigned I, class... T>
using __type_pack_element = typename _type_pack_element_t<I, T...>::Type;
#endif
